#pragma once
#include <functional>
#include "Sock.hpp"
//读到什么返回时什么
namespace select_ns
{
    static const uint16_t defaultPort = 8080;
    static const int fdNum = sizeof(fd_set) * 8;//fd_set是位图结构，*8表示共有几位
    static const int defaultFd = -1;
    static const int bufferNum = 1024;
    using func_t = std::function<std::string(const std::string)>;//传入读到的数据，返回处理后的数据
    class SelectServer
    {
    public:
        SelectServer(func_t func, uint16_t port = defaultPort)
            :_port(port)
            ,_listenSocket(-1)
            ,_fdArray(nullptr)
            ,_func(func)
        {}
        void InitServer()
        {
            _listenSocket = Sock::Socket();
            Sock::Bind(_listenSocket, _port);
            Sock::Listen(_listenSocket);
            _fdArray = new int[fdNum];
            for (int i = 0; i < fdNum; ++i)//清空fd数组
            {
                _fdArray[i] = defaultFd;
            }
            _fdArray[0] = _listenSocket;
            LogMessage(NORMAL,"create sock success %d", _listenSocket);
        }
        void Start()
        {
            while(1) 
            {
                //定义读文件描述符集
                fd_set rfds;
                //清空读文件描述符集，每次循环都需要重新清空
                FD_ZERO(&rfds);
                //最大的文件描述符，给select传参用
                int maxFd = defaultFd;
                for (int i = 0; i < fdNum; ++i)
                {
                    if(_fdArray[i] == defaultFd) continue;
                    //将合法fd全部添加到读文件描述符集中
                    FD_SET(_fdArray[i], &rfds);
                    if(maxFd < _fdArray[i]) maxFd = _fdArray[i];//更新所有fd中的最大fd
                }
                //select多路转接
                //struct timeval timeout = {3, 0};//最多阻塞3秒就让select返回
                //int n = select(_listenSocket + 1, &rfds, nullptr, nullptr, &timeout);//timeout会从三秒减到0，需要手动更新，否则一直是0
                int n = select(maxFd + 1, &rfds, nullptr, nullptr, nullptr);//select阻塞式等待
                switch(n)
                {
                case 0://超时返回
                    LogMessage(NORMAL, "timeOut...");
                    break;
                case -1://select失败
                    LogMessage(WARNING, "select error, code: %d, err string:%s", errno, strerror(errno));
                    break;
                default://事件就绪
                    LogMessage(NORMAL, "have event ready");//如果没有把底层的连接取走，那么_listenSocket将一直就绪
                    HandlerReadEvent(rfds);
                    //HandlerWriteEvent(wfds)
                    //HandlerErrorEvent(efds)
                    break;
                }
            }
        }
        void ListenEvent(int listenSocket)//处理_listenSocket并将新获得的fd加入合法数组中
        {
            //select告诉我，_listenSocket读事件就绪
            std::string clientIp;
            uint16_t clientPort = -1;
            int sock = Sock::Accept(listenSocket, &clientIp, &clientPort);//accept ==等 + 获取  
            if(sock < 0) 
            {
                return;
            }
            //此处不能write/read，因为整个代码只有select有资格检测事件是否就绪.此处应该将新的sock托管给select
            //在数组中寻找新的fd坑位
            int i = 0;
            for(; i < fdNum; ++i)
            {
                if(_fdArray[i] != defaultFd) continue;
                else break;
            }
            if(i == fdNum)//说明文件描述符被写满了
            {
                LogMessage(WARNING, "Server is full, please wait");
                close(sock);//满载关闭消息
            }
            else
            {
                _fdArray[i] = sock;
            }
            //Print();
        }
        void Read(int sock, int pos)//pos是文件描述符在数组中的位置
        {
            //1、读取request（这样读取存在问题，无法保证读到一个完整报文），并且同时可能有多个文件描述符就绪，同时调用这段代码
            char buffer[bufferNum];
            ssize_t s = recv(sock, buffer, sizeof(buffer)-1, 0);
            if(s > 0)
            {
                buffer[s] = 0;
                LogMessage(NORMAL, "client# %s", buffer);
            }
            else if(0 == s)//连接关闭
            {
                close(sock);//close之后，需要在所维护的数组中去掉对应的fd
                _fdArray[pos] = defaultFd;
                LogMessage(NORMAL, "client quit");
                return ;
            }
            else//读取失败
            {
                close(sock);//close之后，需要在所维护的数组中去掉对应的fd
                _fdArray[pos] = defaultFd;
                LogMessage(ERROR, "read fail%s", strerror(errno));
                return ;
            }
            //2、处理request
            std::string response = _func(buffer);
            //3、返回response
            //写事件，有问题的，需要重新维护一个写就绪的文件描述符数组
            write(sock, response.c_str(), response.size());
        }
        void HandlerReadEvent(fd_set& rfds)
        {
            for(int i = 0; i < fdNum; ++i)
            {
                //过滤掉空的fd
                if(_fdArray[i] == defaultFd) continue;
                //但是正常的fd不一定就绪
                //处理_listenSocket
                if(FD_ISSET(_fdArray[i], &rfds) && _fdArray[i] == _listenSocket)//如果_listenSocket在rfds集合里
                {  
                    ListenEvent(_listenSocket);
                }
                else if(FD_ISSET(_fdArray[i], &rfds))//判定该fd有没有就绪
                {
                    Read(_fdArray[i], i);
                }
                else
                {}
            }
        }
        // void Print()//打印合法的文件描述符
        // {
        //     std::cout << "fd list:";
        //     for (int i = 0; i < fdNum; ++i)
        //     {
        //         if(_fdArray[i] != defaultFd)
        //         {
        //             std::cout << _fdArray[i] << " ";
        //         }
        //         std::cout << std::endl;
        //     }
        // }
        ~SelectServer()
        {
            if(_listenSocket >= 0)
            {
                close(_listenSocket); 
            }
            if(_fdArray)
            {
                for(int i = 0; i < fdNum; ++i)
                {
                    if(_fdArray[i] != defaultFd)
                    {
                        close(_fdArray[i]);
                    }
                }
                delete[] _fdArray;
            }
        }
    private:
        uint16_t _port;
        int _listenSocket;
        int* _fdArray;//由程序员维护的合法fd数组
        func_t _func;   
    };
}